#include "../stdafx.h"

#include "../include/Platform.h"
#include "platwin.h"

namespace soy {
	static HCURSOR reverseArrowCursor = NULL;

	Window::~Window() {
	}

	void Window::Destroy() {
		if ( wid ) {
			::DestroyWindow( reinterpret_cast<HWND>( wid ) );
		}

		wid = 0;
	}

	bool Window::HasFocus() {
		return ::GetFocus() == wid;
	}

	Rect Window::GetPosition() {
		RECT rc;
		::GetWindowRect( reinterpret_cast<HWND>( wid ), &rc );
		return Rect( rc.left, rc.top, rc.right, rc.bottom );
	}

	void Window::SetPosition( Rect rc ) {
		::SetWindowPos( reinterpret_cast<HWND>( wid ),
						0, rc.left, rc.top, rc.Width(), rc.Height(), SWP_NOZORDER | SWP_NOACTIVATE );
	}

	void Window::SetPositionRelative( Rect rc, Window w ) {

		LONG style = ::GetWindowLong( reinterpret_cast<HWND>( wid ), GWL_STYLE );

		if ( style & WS_POPUP ) {
			POINT ptOther = {0, 0};
			::ClientToScreen( reinterpret_cast<HWND>( w.GetID() ), &ptOther );
			rc.Move( ptOther.x, ptOther.y );

			// This #ifdef is for VC 98 which has problems with MultiMon.h under some conditions.
#ifdef MONITOR_DEFAULTTONULL
			// We're using the stub functionality of MultiMon.h to decay gracefully on machines
			// (ie, pre Win2000, Win95) that do not support the newer functions.
			RECT rcMonitor = Rect2RECT( rc );
			MONITORINFO mi = {0};
			mi.cbSize = sizeof( mi );

			HMONITOR hMonitor = ::MonitorFromRect( &rcMonitor, MONITOR_DEFAULTTONEAREST );
			// If hMonitor is NULL, that's just the main screen anyways.
			::GetMonitorInfo( hMonitor, &mi );

			// Now clamp our desired rectangle to fit inside the work area
			// This way, the menu will fit wholly on one screen. An improvement even
			// if you don't have a second monitor on the left... Menu's appears half on
			// one screen and half on the other are just U.G.L.Y.!
			if ( rc.right > mi.rcWork.right ) {
				rc.Move( mi.rcWork.right - rc.right, 0 );
			}

			if ( rc.bottom > mi.rcWork.bottom ) {
				rc.Move( 0, mi.rcWork.bottom - rc.bottom );
			}

			if ( rc.left < mi.rcWork.left ) {
				rc.Move( mi.rcWork.left - rc.left, 0 );
			}

			if ( rc.top < mi.rcWork.top ) {
				rc.Move( 0, mi.rcWork.top - rc.top );
			}

#endif
		}

		SetPosition( rc );

	}

	Rect Window::GetClientPosition() {
		RECT rc = {0, 0, 0, 0};

		if ( wid ) {
			::GetClientRect( reinterpret_cast<HWND>( wid ), &rc );
		}

		return  Rect( rc.left, rc.top, rc.right, rc.bottom );
	}

	void Window::Show( bool show ) {
		if ( show ) {
			::ShowWindow( reinterpret_cast<HWND>( wid ), SW_SHOWNOACTIVATE );
		} else {
			::ShowWindow( reinterpret_cast<HWND>( wid ), SW_HIDE );
		}
	}

	void Window::InvalidateAll() {
		::InvalidateRect( reinterpret_cast<HWND>( wid ), NULL, FALSE );
	}

	void Window::InvalidateRectangle( Rect rc ) {
		RECT rcw = Rect2RECT( rc );
		::InvalidateRect( reinterpret_cast<HWND>( wid ), &rcw, FALSE );
	}

	static LRESULT Window_SendMessage( Window* w, UINT msg, WPARAM wParam = 0, LPARAM lParam = 0 ) {
		return ::SendMessage( reinterpret_cast<HWND>( w->GetID() ), msg, wParam, lParam );
	}

	void Window::SetFont( Font& font ) {
		Window_SendMessage( this, WM_SETFONT,
							reinterpret_cast<WPARAM>( font.GetID() ), 0 );
	}

	static void FlipBitmap( HBITMAP bitmap, int width, int height ) {
		HDC hdc = ::CreateCompatibleDC( NULL );

		if ( hdc != NULL ) {
			HGDIOBJ prevBmp = ::SelectObject( hdc, bitmap );
			::StretchBlt( hdc, width - 1, 0, -width, height, hdc, 0, 0, width, height, SRCCOPY );
			::SelectObject( hdc, prevBmp );
			::DeleteDC( hdc );
		}
	}

	static HCURSOR GetReverseArrowCursor() {
		if ( reverseArrowCursor != NULL ) {
			return reverseArrowCursor;
		}
		LockSection();
		HCURSOR cursor = reverseArrowCursor;

		if ( cursor == NULL ) {
			cursor = ::LoadCursor( NULL, IDC_ARROW );
			ICONINFO info;

			if ( ::GetIconInfo( cursor, &info ) ) {
				BITMAP bmp;

				if ( ::GetObject( info.hbmMask, sizeof( bmp ), &bmp ) ) {
					FlipBitmap( info.hbmMask, bmp.bmWidth, bmp.bmHeight );

					if ( info.hbmColor != NULL ) {
						FlipBitmap( info.hbmColor, bmp.bmWidth, bmp.bmHeight );
					}

					info.xHotspot = ( DWORD )bmp.bmWidth - 1 - info.xHotspot;

					reverseArrowCursor = ::CreateIconIndirect( &info );

					if ( reverseArrowCursor != NULL ) {
						cursor = reverseArrowCursor;
					}
				}

				::DeleteObject( info.hbmMask );

				if ( info.hbmColor != NULL ) {
					::DeleteObject( info.hbmColor );
				}
			}
		}

		UnlockSection();
		return cursor;
	}

	void Window::SetCursor( Cursor curs ) {
		switch ( curs ) {
		case cursorText:
			::SetCursor( ::LoadCursor( NULL, IDC_IBEAM ) );
			break;

		case cursorUp:
			::SetCursor( ::LoadCursor( NULL, IDC_UPARROW ) );
			break;

		case cursorWait:
			::SetCursor( ::LoadCursor( NULL, IDC_WAIT ) );
			break;

		case cursorHoriz:
			::SetCursor( ::LoadCursor( NULL, IDC_SIZEWE ) );
			break;

		case cursorVert:
			::SetCursor( ::LoadCursor( NULL, IDC_SIZENS ) );
			break;

		case cursorHand:
			::SetCursor( ::LoadCursor( NULL, IDC_HAND ) );
			break;

		case cursorReverseArrow:
			::SetCursor( GetReverseArrowCursor() );
			break;

		case cursorArrow:
		case cursorInvalid:	// Should not occur, but just in case.
			::SetCursor( ::LoadCursor( NULL, IDC_ARROW ) );
			break;
		}
	}

	void Window::SetTitle( const char* s ) {
		::SetWindowTextA( reinterpret_cast<HWND>( wid ), s );
	}

	/* Returns rectangle of monitor pt is on, both rect and pt are in Window's
	   coordinates */
	Rect Window::GetMonitorRect( Point pt ) {
#ifdef MONITOR_DEFAULTTONULL
		// MonitorFromPoint and GetMonitorInfo are not available on Windows 95 so are not used.
		// There could be conditional code and dynamic loading in a future version
		// so this would work on those platforms where they are available.
		Rect rcPosition = GetPosition();
		POINT ptDesktop = {static_cast<LONG>( pt.x + rcPosition.left ),
						   static_cast<LONG>( pt.y + rcPosition.top )
						  };
		HMONITOR hMonitor = ::MonitorFromPoint( ptDesktop, MONITOR_DEFAULTTONEAREST );
		MONITORINFO mi = {0};
		memset( &mi, 0, sizeof( mi ) );
		mi.cbSize = sizeof( mi );

		if ( ::GetMonitorInfo( hMonitor, &mi ) ) {
			Rect rcMonitor(
				mi.rcWork.left - rcPosition.left,
				mi.rcWork.top - rcPosition.top,
				mi.rcWork.right - rcPosition.left,
				mi.rcWork.bottom - rcPosition.top );
			return rcMonitor;
		} else {
			return Rect();
		}

#else
		return Rect();
#endif
	}


};
